#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <errno.h>

//#include <linux/spi/ipcl_common.h>
#include "ipcl_common.h" 

#include "update_image_decode.h"
#include "update_vip.h"

//#define DEBUG_CODE

static struct update_info *pinfo = NULL;

static int update_ready_request(void)
{
	uint8_t buf[5] = {0};
	int ret = 0;
	
	buf[0] = 0x00;
	buf[1] = 0x05;
	buf[2] = GROUP_UPDATE_ID;
	buf[3] = HOST2VIP_MSG_REQUEST;
	buf[4] = UPDATE_HOST_REQ_UPDATE_READY;

	ret = write(pinfo->update_fd, buf, sizeof(buf));
	if (ret != sizeof(buf)) {
		printf("Send update_ready_request failed\n");
		return -1;
	}

	pinfo->recv_flag = 0;
#ifdef DEBUG_CODE
	printf("Send update_ready_request.\n");
#endif
	return ret;
}

static int send_data(uint16_t *data_index)
{
	uint8_t buf[UPDATE_SEND_BUF_LEN] = {0};
	uint16_t msg_len = 0;
	uint16_t msg_len_be16;
	uint32_t addr;
	uint32_t addr_be32;
	uint16_t data_len;
	uint16_t data_len_be16;
	struct data_frame *df;
	int ret;

	msg_len += IPCL_MSG_LEN_SIZE;

	buf[IPCL_MSG_GROUP_OFFSET] = GROUP_UPDATE_ID;
	buf[IPCL_MSG_MAJOR_OFFSET] = HOST2VIP_MSG_REQUEST;
	buf[IPCL_MSG_SUB_OFFSET] = UPDATE_HOST_REQ_SEND_DATA;
	msg_len += 3;
	
	addr = df_list->next->addr;
	addr_be32 = htonl(addr);
	memcpy(buf + IPCL_MSG_PAYLOAD_OFFSET, &addr_be32, RECORED_ADDR_BYTES);
	msg_len += RECORED_ADDR_BYTES;
	
	data_len = df_list->next->len;
	data_len_be16 = htons(data_len);
	memcpy(buf + IPCL_MSG_PAYLOAD_OFFSET + RECORED_ADDR_BYTES,
			&data_len_be16, RECORED_LEN_BYTES);
	msg_len += RECORED_LEN_BYTES;

	memcpy(buf + IPCL_MSG_PAYLOAD_OFFSET +
			RECORED_ADDR_BYTES + RECORED_LEN_BYTES,
			df_list->next->buf, df_list->next->len);
	msg_len += df_list->next->len;

	msg_len_be16 = htons(msg_len);
	memcpy(buf, &msg_len_be16, IPCL_MSG_LEN_SIZE);

	ret = write(pinfo->update_fd, buf, msg_len);
	if (ret != msg_len) {
		printf("Send data failed!\n");
		return -1;
	}

	*data_index = df_list->next->index;
	pinfo->recv_flag = 0;

#ifdef DEBUG_CODE
	printf("Send data_frame.\n");
#endif
	return ret;
}

static int send_checksum_request(void)
{
	unsigned char buf[5] = {0};
	int ret = 0;

	buf[0] = 0x00;
	buf[1] = 0x05;
	buf[2] = GROUP_UPDATE_ID;
	buf[3] = HOST2VIP_MSG_REQUEST;
	buf[4] = UPDATE_HOST_REQ_CHECK_CS;

	ret = write(pinfo->update_fd, buf, sizeof(buf));
	if (ret != sizeof(buf)) {
		printf("send_checksum_request failed!\n");
		return -1;
	}

	pinfo->recv_flag = 0;

#ifdef DEBUG_CODE
	printf("Send checksum request.\n");
#endif
	return ret;
}

static void send_update_rate(uint16_t last_data_index)
{
	uint8_t rate_str[10];
	uint8_t update_rate;
	int ret;

	putchar('.');
	fflush(stdout);
	if ((last_data_index % 60 == 0) &&
			(last_data_index >= 60))
		printf("\n");

	update_rate = last_data_index * 100 / pinfo->data_frame_num;
	if (100 == update_rate)
		printf("\n");

	if (pinfo->update_rate != update_rate)
	{
		pinfo->update_rate = update_rate;
		snprintf(rate_str, 10, "%d", update_rate);

		ret = write(pinfo->update_rate_fd,
			rate_str, strlen(rate_str) + 1);
		if (ret != strlen(rate_str) + 1)
			printf("Failed to send update rate!\n");
#ifdef DEBUG_CODE
		printf("Current rate is %d\n", update_rate);
#endif
	}
}

static void *receive_thread(void *arg)
{
	int ret;

	while (1)
	{
		usleep(100);
		if (!pinfo->recv_flag)
		{
			ret = read(pinfo->update_fd,
					pinfo->buf, UPDATE_RECV_BUF_LEN);
			if (ret > 0)
			{
				pinfo->recv_flag = 1;
#ifdef DEBUG_CODE
				printf("Received %d bytes from %s.\n",
						ret, UPDATE_DEV);
#endif
			}
		}
	}
}

static int reset_vip(UPDATE_PHASE_T phase)
{
	uint8_t buf[5] = {0};
	int ret = 0;

	buf[0] = 0x00;
	buf[1] = 0x05;
	buf[2] = GROUP_POWER_ID;
	buf[3] = HOST2VIP_MSG_REQUEST;

	switch (phase) {
	case UPDATE_APP_PHASE:
		buf[4] = PWR_HOST_REQ_REBOOT_SWL;
		break;
	case UPDATE_SWL_PHASE:
		buf[4] = PWR_HOST_REQ_REBOOT_APP;
		break;
	default:
		printf("Phase(%d) doesn't exist!\n", phase);
		return -1;
	}

	ret = write(pinfo->update_fd, buf, sizeof(buf));
	if (ret != sizeof(buf))
	{
		printf("Send reboot request failed!\n");
		return -1;
	}

#ifdef DEBUG_CODE
	printf("Reboot to phase(%d).\n", buf[4]);
#endif

	return 0;
}

static int update_vip(void)
{
	UPDATE_VIP_RESPOND_T msg_type;
	int err;
	char *buf = pinfo->buf;
	int len;
	int ret = 0;
	static uint8_t retry = 2;
	
	ret = update_ready_request();
	if (ret < 0)
		return ret;
	
	pinfo->data_frame_num = df_list->last->index;

	while (1)
	{
		usleep(100);
		if (!pinfo->recv_flag)
			continue;

		msg_type = buf[IPCL_MSG_SUB_OFFSET];
		switch (msg_type) {
		case UPDATE_VIP_RESP_UPDATE_READY:
		{
			switch (buf[IPCL_MSG_PAYLOAD_OFFSET]) {
			case UPDATE_STATUS_SUCCESS:
#ifdef DEBUG_CODE
				printf("VIP is ready to recv data.\n");
#endif
				memset(buf, 0, UPDATE_RECV_BUF_LEN); 
				ret = send_data(&pinfo->data_index);
				if (ret < 0) {
					printf("Send data error!\n");
					return ret;
				}

				break;
			case UPDATE_STATUS_ERASE_FLASH_FAIL:
				printf("VIP failed to erase flash.\n");
				return -1;
			default:
				printf("VIP failed to get ready!\n");
				return -1;
			}

			break;
		}
		case UPDATE_VIP_RESP_SEND_DATA:
		{
			switch (buf[IPCL_MSG_PAYLOAD_OFFSET]) {
			case UPDATE_STATUS_SUCCESS:
				retry = 2;
#ifdef DEBUG_CODE
				printf("VIP recv data(%d) successfully.\n",
						pinfo->data_index);
#endif
				memset(buf, 0, UPDATE_RECV_BUF_LEN);
				/* delete the sent data frame from the list */
				df_list_remove_first();
				send_update_rate(pinfo->data_index);

				if (pinfo->data_frame_num == pinfo->data_index) {
#ifdef DEBUG_CODE
					printf("Data transfer complete.\n");
#endif
					ret = send_checksum_request();
					if (ret < 0) {
						printf("Send checksum_request failed!\n");
						return ret;
					}

					continue;
				}

				break;
			case UPDATE_STATUS_NO_MEM:
				printf("\nVIP has no memory to hold data.\n");
				if (!retry)
					return -1;
				printf("Retry one more time.\n");
				retry--;

				break;
			default:
				printf("\nVIP failed to recv data!\n");
				if (!retry)
					return -1;
				printf("Retry one more time.\n");
				retry--;

				break;
			}

			memset(buf, 0, UPDATE_RECV_BUF_LEN);
			ret = send_data(&pinfo->data_index);
			if (ret < 0) {
				printf("Send data error!\n");
				return ret;
			}

			break;
		}
		case UPDATE_VIP_RESP_CHECK_CS:
		{
			switch (buf[IPCL_MSG_PAYLOAD_OFFSET]) {
			case UPDATE_STATUS_SUCCESS:
				send_update_rate(pinfo->data_index);
			
				printf("Checksum is true, send complete!\n");

				return 0;
			default:
				printf("Checksum is wrong!\n");

				return -1;
			}
        	}
		default:
			printf("Unknown msg type!\n");
			return -1;
		}
	}
}

static int open_device(void)
{
	pinfo->update_fd = open(UPDATE_DEV, O_RDWR | O_SYNC);
	if (pinfo->update_fd < 0)
	{
		printf("Can not open DEV\n");
		return -1;
	}

	pinfo->update_rate_fd = open(UPDATE_RATE_DEV, O_WRONLY | O_SYNC);
	if (pinfo->update_rate_fd < 0)
	{
		printf("Can not open UPDATE_RATE_DEV\n");
		close(pinfo->update_fd);
		return -1;
	}
	
	return 0;
}

static void close_device(void)
{
	close(pinfo->update_fd);
	close(pinfo->update_rate_fd);
}

static int create_recv_thread(void)
{
	int ret = 0;
	pthread_t ntid;

	ret = pthread_create(&ntid, NULL, receive_thread, NULL);
	if (ret < 0)
	    perror("pthread_create error\n");

	return ret;
}

static void get_bin_path(UPDATE_PHASE_T phase, uint8_t *path)
{
	switch (phase) {
	case UPDATE_APP_PHASE:
		path = SWL_PATH;
		break;
	case UPDATE_SWL_PHASE:
		path = APP_PATH;
		break;
	default:
		break;
	}
}

static int init_update_info(void)
{
	pinfo = calloc(1, sizeof(*pinfo));
	if (!pinfo) {
		printf("Alloc update_info failed!\n");
		return -1;
	}

	pinfo->buf = calloc(1, UPDATE_RECV_BUF_LEN);
	if (!pinfo->buf) {
		printf("Alloc recv buf failed!\n");
		return -1;
	}

	pinfo->recv_flag = 1;
	pinfo->update_fd = -1;
	pinfo->update_rate_fd = -1;

	return 0;
}

static void free_update_info(void)
{
	free(pinfo->buf);
	free(pinfo);
}

static void print_usage(const char *prog)
{
	printf("Usage: %s [-pl]\n", prog);
	puts(	"  -p --phase		set update phase\n"
		"  -l --locatioin	set image path\n"
		);
	exit(1);
}

static void parse_opts(int argc, char *argv[],
			UPDATE_PHASE_T *phase, uint8_t **path)
{
	static const struct option lopts[] = {
		{ "phase",	1, 0, 'p' },
		{ "location",	1, 0, 'l' },
		{ NULL, 0, 0, 0 },
	};
	int c;
	FILE *fp = NULL;

	if (argc < 2)
		print_usage(argv[0]);

	while (1) {
		c = getopt_long(argc, argv, "p:l:", lopts, NULL);
		if (c == -1)
			break;

		switch (c) {
		case 'p':
			*phase = atoi(optarg);
			break;
		case 'l':
			*path = optarg;
			break;
		default:
			print_usage(argv[0]);
			break;
		}

		if (*phase > UPDATE_PHASE_MAX) {
			printf("Phase value(%d) is incorrect!\n", *phase);
			exit(1);
		}

		if (!*path)
			continue;
		fp = fopen(*path, "r");
		if (!fp) {
			printf("Image path is incorrect!\n");
			exit(1);
		}
		fclose(fp);
	}
}

int main(int argc, char *argv[])
{
	int ret = 0;
	UPDATE_PHASE_T phase = UPDATE_APP_PHASE;
	uint8_t *path = NULL;

#define MANUAL_EXECUTE
#ifdef MANUAL_EXECUTE
	parse_opts(argc, argv, &phase, &path);
#ifdef DEBUG_CODE
	printf("phase(%d), path=%s\n", phase, path);
#endif
#else
	if (argc < 3) {
		printf("Arguments are not enough!\n");
		exit(1);
	}
	else {
		phase = atoi(argv[1]);
		if (phase > UPDATE_PHASE_MAX) {
			printf("Phase value is incorrect!\n");
			exit(1);
		}
		path = argv[2];
	}
#endif

	printf("Start update VUC process!\n");

	ret = load_image(path);
	if (ret < 0) {
		printf("Load image failed!\n");
		return ret;
	}

	ret = init_update_info();
	if (ret < 0) {
		printf("Init update info failed!\n");
		return ret;
	}

	ret = open_device();
	if (ret < 0) {
		printf("Open device failed\n");
		goto free_pinfo;
	}

	ret = create_recv_thread();
	if (ret < 0) {
		printf("Create recv thread failed!\n");
		goto fail;
	}

	ret = update_vip();
	if (ret < 0) {
		printf("Update firmware failed!\n");
		goto fail;
	}

#if 0
	ret = reset_vip(phase);
	if (ret < 0) {
		printf("Reset vip failed!\n");
		goto fail;
	}
#endif

	close_device();
	df_list_free();
	free_update_info();
	return ret;
fail:
	close_device();
free_pinfo:
	df_list_free();
	free_update_info();
	return ret;
};
